/*
 *  matiec - a compiler for the programming languages defined in IEC 61131-3
 *
 *  Copyright (C) 2021  Mario de Sousa (msousa@fe.up.pt)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * This code is made available on the understanding that it will not be
 * used in safety-critical situations without a full and competent review.
 */

/*
 * An IEC 61131-3 compiler.
 *
 * Based on the
 * FINAL DRAFT - IEC 61131-3, 2nd Ed. (2001-12-10)
 *
 */


/*
 * Code to be included into the code generated by the 4th stage.
 *
 * This is part of the 4th stage that generates
 * a summary of the call graph in JSON format.
 * This is file is to be used by ...
 */



static int  entry_count = 0;
static bool enable_print = true;


// #include <stdio.h>  /* required for NULL */
#include <string.h>  // strdup(), strchr()
#include <string>
#include <iostream>
#include <sstream>
#include <typeinfo>
#include "generate_json.hh"

#include "../stage4.hh"
#include "../../main.hh" // required for ERROR() and ERROR_MSG() macros.

#include "../../absyntax_utils/absyntax_utils.hh"


#define STAGE4_ERROR(symbol1, symbol2, ...)   {stage4err ("while generating JSON", symbol1, symbol2, __VA_ARGS__); exit(EXIT_FAILURE);}
#define STAGE4_WARNING(symbol1, symbol2, ...) {stage4warn("while generating JSON", symbol1, symbol2, __VA_ARGS__);}


/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/

/* Global variables containing user defined options */
static stage4out_c *_s4o = NULL;


/* Parse command line options passed from main.c !! */

int  stage4_parse_options(char *options) {return 0;}

void stage4_print_options(void) {
  printf("          (no options available when generating JSON summary of 61131-3 code)\n"); 
}




/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/





class localvar_list_builder_c: public iterator_visitor_c {
  public:
    typedef symtable_c<symbol_c *> localvar_symtable_t;
  
  private:
    localvar_symtable_t *current_localvar_list;

  public:
    localvar_list_builder_c(void) {current_localvar_list = NULL;}
    ~localvar_list_builder_c(void) {}

    void populate_list(localvar_symtable_t *localvar_list, symbol_c *pou_decl) {
      current_localvar_list = localvar_list;
      pou_decl->accept(*this);
      current_localvar_list = NULL;
      return;
    }

    
  private:
    void add_symbol_to_list(symbol_c *token) {
      current_localvar_list->insert(token, token);
    }  

    void add_list_to_list(list_c *list) {
      for(int i = 0; i < list->n; i++)
        add_symbol_to_list(list->get_element(i));
    }  


/******************************************/
/* B 1.4.3 - Declaration & Initialisation */
/******************************************/
/* VAR_INPUT [RETAIN | NON_RETAIN] input_declaration_list END_VAR */
/* option -> the RETAIN/NON_RETAIN/<NULL> directive... */
void *visit(input_declarations_c *symbol) {
  if (typeid(*(symbol->method)) == typeid(explicit_definition_c))
    symbol->input_declaration_list->accept(*this);
  return NULL;
}

/* edge -> The F_EDGE or R_EDGE directive */
void *visit(edge_declaration_c *symbol) {
  return symbol->var1_list->accept(*this);  // points to a var1_list_c
}

/* EN : BOOL := 1 */
void *visit(en_param_declaration_c *symbol) {
  if (typeid(*(symbol->method)) == typeid(explicit_definition_c))
    add_symbol_to_list(symbol->name);
  return NULL;
}

/* ENO : BOOL */
void *visit(eno_param_declaration_c *symbol) {
  if (typeid(*(symbol->method)) == typeid(explicit_definition_c))
    add_symbol_to_list(symbol->name);
  return NULL;
}


/* one of the following...
 *   var1_list ':' simple_specification
 *   var1_list ':' subrange_spec_init
 *   var1_list ':' enumerated_spec_init
 * 
 *   simple_specification -> simple_spec_init_c
 *   subrange_spec_init   -> simple_spec_init_c
 *   enumerated_spec_init -> enumerated_spec_init_c
 *   var1_list            -> var1_list_c
 */
void *visit(var1_init_decl_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}


void *visit(var1_list_c *symbol) {
  add_list_to_list(symbol);
  return NULL;
}


/* | [var1_list ','] variable_name '..' */
/* NOTE: This is an extension to the standard!!! */
/* In order to be able to handle extensible standard functions
 * (i.e. standard functions that may have a variable number of
 * input parameters, such as AND(word#33, word#44, word#55, word#66),
 * we have extended the acceptable syntax to allow var_name '..'
 * in an input variable declaration.
 *
 * This allows us to parse the declaration of standard
 * extensible functions and load their interface definition
 * into the abstract syntax tree just like we do to other 
 * user defined functions.
 * This has the advantage that we can later do semantic
 * checking of calls to functions (be it a standard or user defined
 * function) in (almost) exactly the same way.
 *
 * Of course, we have a flag that disables this syntax when parsing user
 * written code, so we only allow this extra syntax while parsing the 
 * 'header' file that declares all the standard IEC 61131-3 functions.
 */
void *visit(extensible_input_parameter_c *symbol) {
  ERROR; // this should never get called, as this symbol should always be inside a var1_list_c
  add_symbol_to_list(symbol->var_name);
  return NULL;
}


/* var1_list ':' array_spec_init */
void *visit(array_var_init_decl_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}


/*  var1_list ':' initialized_structure */
void *visit(structured_var_init_decl_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}

/* name_list ':' function_block_type_name ASSIGN structure_initialization */
/* structure_initialization -> may be NULL ! */
void *visit(fb_name_decl_c *symbol) {
  return symbol->fb_name_list->accept(*this); // points to fb_name_list_c
}

void *visit(fb_name_list_c *symbol) {
  add_list_to_list(symbol);
  return NULL;
}


/* VAR_OUTPUT [RETAIN | NON_RETAIN] var_init_decl_list END_VAR */
/* option -> may be NULL ! */
void *visit(output_declarations_c *symbol) {
  if (typeid(*(symbol->method)) == typeid(explicit_definition_c)) {
    symbol->var_init_decl_list->accept(*this);
  }
  return NULL;
}

/*  VAR_IN_OUT  END_VAR */
void *visit(input_output_declarations_c *symbol) {
  return symbol->var_declaration_list->accept(*this);
}

/* helper symbol for input_output_declarations */
/* var_declaration_list var_declaration ';' */
// Leave it to underlying iterator_visitor_c
//void *visit(var_declaration_list_c *symbol)

/*  var1_list ':' array_specification */
void *visit(array_var_declaration_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}

/*  var1_list ':' structure_type_name */
void *visit(structured_var_declaration_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}

/* VAR [CONSTANT] var_init_decl_list END_VAR */
/* option -> may be NULL ! */
void *visit(var_declarations_c *symbol) {
  return symbol->var_init_decl_list->accept(*this);
}

/*  VAR RETAIN var_init_decl_list END_VAR */
void *visit(retentive_var_declarations_c *symbol) {
  return symbol->var_init_decl_list->accept(*this);
}

/*  VAR [CONSTANT|RETAIN|NON_RETAIN] located_var_decl_list END_VAR */
/* option -> may be NULL ! */
void *visit(located_var_declarations_c *symbol) {
  return symbol->located_var_decl_list->accept(*this);
}

/* helper symbol for located_var_declarations */
/* located_var_decl_list located_var_decl ';' */
// leave to the underlysing interator_visitor_c
// void *visit(located_var_decl_list_c *symbol) {}

/*  [variable_name] location ':' located_var_spec_init */
/* variable_name -> may be NULL ! */
void *visit(located_var_decl_c *symbol) {
  if (symbol->variable_name != NULL) {
    add_symbol_to_list(symbol->variable_name);
  }
  //symbol->location->accept(*this);
  return NULL;
}


/*| VAR_EXTERNAL [CONSTANT] external_declaration_list END_VAR */
/* option -> may be NULL ! */
void *visit(external_var_declarations_c *symbol) {
  // External variables reference global variables 
  // which we do NOT want to add to the list of local variabçes
  // return symbol->external_declaration_list->accept(*this);
  return NULL;
}

/* helper symbol for external_var_declarations */
/*| external_declaration_list external_declaration';' */
// leave to underlying iterator_visitor_c
// void *visit(external_declaration_list_c *symbol)


/*  global_var_name ':' (simple_specification|subrange_specification|enumerated_specification|array_specification|prev_declared_structure_type_name|function_block_type_name) */
void *visit(external_declaration_c *symbol) {
  ERROR; // should never get called!
  // we do NOT want to add global variables to the list
  // symbol->global_var_name->accept(*this);
  return NULL;
}

/*| VAR_GLOBAL [CONSTANT|RETAIN] global_var_decl_list END_VAR */
/* option -> may be NULL ! */
void *visit(global_var_declarations_c *symbol) {
  // we do NOT want to add global variables to the list
  // symbol->global_var_decl_list->accept(*this);
  return NULL;
}

/* helper symbol for global_var_declarations */
/*| global_var_decl_list global_var_decl ';' */
void *visit(global_var_decl_list_c *symbol) {
  ERROR; // should never get called!
  // we do NOT want to add global variables to the list
  // symbol->global_var_name->accept(*this);
  return NULL;
}

/*| global_var_spec ':' [located_var_spec_init|function_block_type_name] */
/* type_specification ->may be NULL ! */
void *visit(global_var_decl_c *symbol) {
  ERROR; // should never get called!
  // we do NOT want to add global variables to the list
  // symbol->global_var_name->accept(*this);
  return NULL;
}

/*| global_var_name location */
void *visit(global_var_spec_c *symbol) {
  ERROR; // should never get called!
  // we do NOT want to add global variables to the list
  // symbol->global_var_name->accept(*this);
  //symbol->global_var_name->accept(*this);
  //symbol->location->accept(*this);
  return NULL;
}

/*  AT direct_variable */
void *visit(location_c *symbol) {
  ERROR; // should never get called!
  // we do NOT want to add global variables to the list
  // symbol->global_var_name->accept(*this);
  // symbol->direct_variable->accept(*this);
  return NULL;
}

/*| global_var_list ',' global_var_name */
void *visit(global_var_list_c *symbol) {
  ERROR; // should never get called!
  // we do NOT want to add global variables to the list
  return NULL;
  
}

/*  var1_list ':' single_byte_string_spec */
void *visit(single_byte_string_var_declaration_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}


/*  var1_list ':' double_byte_string_spec */
void *visit(double_byte_string_var_declaration_c *symbol) {
  return symbol->var1_list->accept(*this); // points to var1_list_c
}


/*| VAR [RETAIN|NON_RETAIN] incompl_located_var_decl_list END_VAR */
/* option ->may be NULL ! */
void *visit(incompl_located_var_declarations_c *symbol) {
  return symbol->incompl_located_var_decl_list->accept(*this);
}

/* helper symbol for incompl_located_var_declarations */
/*| incompl_located_var_decl_list incompl_located_var_decl ';' */
// leave to underlying iterator_visitor_c
// void *visit(incompl_located_var_decl_list_c *symbol)

/*  variable_name incompl_location ':' var_spec */
void *visit(incompl_located_var_decl_c *symbol) {
  add_symbol_to_list(symbol->variable_name);
  // symbol->incompl_location->accept(*this);
  // symbol->var_spec->accept(*this);
  return NULL;
}


/*  AT incompl_location_token */
void *visit(incompl_location_c *symbol) {
  ERROR; // should never get called!
  return NULL;
}


/* intermediate helper symbol for:
 *  - non_retentive_var_decls
 *  - output_declarations
 */
/* | var_init_decl_list var_init_decl ';' */
// leave to underlying iterator_visitor_c
// void *visit(var_init_decl_list_c *symbol)


/***********************/
/* B 1.5.1 - Functions */
/***********************/
void *visit(function_declaration_c *symbol) {
  // function name works as a local variable -> add it to the list
  add_symbol_to_list(symbol->derived_function_name);
  symbol->var_declarations_list->accept(*this);
  // symbol->function_body->accept(*this);
  return NULL;
}

/* (VAR xxxx_decl_list END_VAR)  <-- list of  */
// leave to underlying iterator_visitor_c
// void *visit(var_declarations_list_c *symbol) {return print_list(symbol);}

/* (VAR [CONSTANT] var2_init_decl_list END_VAR)  <-- list of */
// leave to underlying iterator_visitor_c
// void *visit(function_var_decls_c *symbol)

/* VAR [CONSTANT] var2_init_decl_list END_VAR  
 *                ^^^^^^^^^^^^^^^^^^^
 * intermediate helper symbol for function_var_decls 
 */
// leave to underlying iterator_visitor_c
// void *visit(var2_init_decl_list_c *symbol)


/*****************************/
/* B 1.5.2 - Function Blocks */
/*****************************/
/*  FUNCTION_BLOCK derived_function_block_name io_OR_other_var_declarations function_block_body END_FUNCTION_BLOCK */
void *visit(function_block_declaration_c *symbol) {
  return symbol->var_declarations->accept(*this);
}

/*  VAR_TEMP temp_var_decl_list END_VAR */
void *visit(temp_var_decls_c *symbol) {
  return symbol->var_decl_list->accept(*this);
}

// leave to underlying iterator_visitor_c
/* intermediate helper symbol for temp_var_decls */
// void *visit(temp_var_decls_list_c *symbol)

/*  VAR NON_RETAIN var_init_decl_list END_VAR */
void *visit(non_retentive_var_decls_c *symbol) {
  return symbol->var_decl_list->accept(*this);
}


/**********************/
/* B 1.5.3 - Programs */
/**********************/
/*  PROGRAM program_type_name program_var_declarations_list function_block_body END_PROGRAM */
void *visit(program_declaration_c *symbol) {
  return symbol->var_declarations->accept(*this);
}



}; // localvar_list_builder_c







/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/


class print_datatype_c: public iterator_visitor_c {
  private:
    stage4out_c &s4o;
    static print_datatype_c *print_datatype; // singleton


  public:
    print_datatype_c(stage4out_c *s4o_ptr): s4o(*s4o_ptr) {}
    ~print_datatype_c(void) {}
    
    static print_datatype_c *get_singleton(void) {
        if (NULL == print_datatype)
          print_datatype = new print_datatype_c(_s4o);
        return print_datatype;
    }


  private:

    
void *print_token(symbol_c *token) {
  return s4o.print(token->token->value);
}

/*******************************************/
/* B 1.1 - Letters, digits and identifiers */
/*******************************************/
void *visit(                 identifier_c *symbol) {return print_token(symbol);}
void *visit(derived_datatype_identifier_c *symbol) {return print_token(symbol);}
void *visit(         poutype_identifier_c *symbol) {return print_token(symbol);}


/**********************/
/* B.1.3 - Data types */
/**********************/

/***********************************/
/* B 1.3.1 - Elementary Data Types */
/***********************************/
void *visit(       time_type_name_c *symbol) {s4o.print("TIME");        return NULL;}
void *visit(       bool_type_name_c *symbol) {s4o.print("BOOL");        return NULL;}
void *visit(       sint_type_name_c *symbol) {s4o.print("SINT");        return NULL;}
void *visit(        int_type_name_c *symbol) {s4o.print("INT");         return NULL;}
void *visit(       dint_type_name_c *symbol) {s4o.print("DINT");        return NULL;}
void *visit(       lint_type_name_c *symbol) {s4o.print("LINT");        return NULL;}
void *visit(      usint_type_name_c *symbol) {s4o.print("USINT");       return NULL;}
void *visit(       uint_type_name_c *symbol) {s4o.print("UINT");        return NULL;}
void *visit(      udint_type_name_c *symbol) {s4o.print("UDINT");       return NULL;}
void *visit(      ulint_type_name_c *symbol) {s4o.print("ULINT");       return NULL;}
void *visit(       real_type_name_c *symbol) {s4o.print("REAL");        return NULL;}
void *visit(      lreal_type_name_c *symbol) {s4o.print("LREAL");       return NULL;}
void *visit(       date_type_name_c *symbol) {s4o.print("DATE");        return NULL;}
void *visit(        tod_type_name_c *symbol) {s4o.print("TOD");         return NULL;}
void *visit(         dt_type_name_c *symbol) {s4o.print("DT");          return NULL;}
void *visit(       byte_type_name_c *symbol) {s4o.print("BYTE");        return NULL;}
void *visit(       word_type_name_c *symbol) {s4o.print("WORD");        return NULL;}
void *visit(      lword_type_name_c *symbol) {s4o.print("LWORD");       return NULL;}
void *visit(      dword_type_name_c *symbol) {s4o.print("DWORD");       return NULL;}
void *visit(     string_type_name_c *symbol) {s4o.print("STRING");      return NULL;}
void *visit(    wstring_type_name_c *symbol) {s4o.print("WSTRING");     return NULL;}

void *visit(       void_type_name_c *symbol) {s4o.print("VOID");        return NULL;} /* a non-standard extension! */

void *visit(   safetime_type_name_c *symbol) {s4o.print("SAFETIME");    return NULL;}
void *visit(   safebool_type_name_c *symbol) {s4o.print("SAFEBOOL");    return NULL;}
void *visit(   safesint_type_name_c *symbol) {s4o.print("SAFESINT");    return NULL;}
void *visit(    safeint_type_name_c *symbol) {s4o.print("SAFEINT");     return NULL;}
void *visit(   safedint_type_name_c *symbol) {s4o.print("SAFEDINT");    return NULL;}
void *visit(   safelint_type_name_c *symbol) {s4o.print("SAFELINT");    return NULL;}
void *visit(  safeusint_type_name_c *symbol) {s4o.print("SAFEUSINT");   return NULL;}
void *visit(   safeuint_type_name_c *symbol) {s4o.print("SAFEUINT");    return NULL;}
void *visit(  safeudint_type_name_c *symbol) {s4o.print("SAFEUDINT");   return NULL;}
void *visit(  safeulint_type_name_c *symbol) {s4o.print("SAFEULINT");   return NULL;}
void *visit(   safereal_type_name_c *symbol) {s4o.print("SAFEREAL");    return NULL;}
void *visit(  safelreal_type_name_c *symbol) {s4o.print("SAFELREAL");   return NULL;}
void *visit(   safedate_type_name_c *symbol) {s4o.print("SAFEDATE");    return NULL;}
void *visit(    safetod_type_name_c *symbol) {s4o.print("SAFETOD");     return NULL;}
void *visit(     safedt_type_name_c *symbol) {s4o.print("SAFEDT");      return NULL;}
void *visit(   safebyte_type_name_c *symbol) {s4o.print("SAFEBYTE");    return NULL;}
void *visit(   safeword_type_name_c *symbol) {s4o.print("SAFEWORD");    return NULL;}
void *visit(  safelword_type_name_c *symbol) {s4o.print("SAFELWORD");   return NULL;}
void *visit(  safedword_type_name_c *symbol) {s4o.print("SAFEDWORD");   return NULL;}
void *visit( safestring_type_name_c *symbol) {s4o.print("SAFESTRING");  return NULL;}
void *visit(safewstring_type_name_c *symbol) {s4o.print("SAFEWSTRING"); return NULL;}

/********************************/
/* B.1.3.2 - Generic data types */
/********************************/
void *visit(generic_type_any_c      *symbol) {s4o.print("ANY");         return NULL;}

/********************************/
/* B 1.3.3 - Derived data types */
/********************************/

/*  simple_type_name ':' simple_spec_init */
void *visit(simple_type_declaration_c *symbol) {
  return print_token(symbol->simple_type_name);
}

/*  subrange_type_name ':' subrange_spec_init */
void *visit(subrange_type_declaration_c *symbol) {
  return print_token(symbol->subrange_type_name);
}


/*  enumerated_type_name ':' enumerated_spec_init */
void *visit(enumerated_type_declaration_c *symbol) {
  return print_token(symbol->enumerated_type_name);
}


/*  identifier ':' array_spec_init */
void *visit(array_type_declaration_c *symbol) {
  return print_token(symbol->identifier);
}


/*  structure_type_name ':' structure_specification */
void *visit(structure_type_declaration_c *symbol) {
  return print_token(symbol->structure_type_name);
}


/*  string_type_name ':' elementary_string_type_name string_type_declaration_size string_type_declaration_init */
void *visit(string_type_declaration_c *symbol) {
  return print_token(symbol->string_type_name);
}

/*  function_block_type_name ASSIGN structure_initialization */
/* structure_initialization -> may be NULL ! */
void *visit(fb_spec_init_c *symbol) {
  // NOTE : This visitor should not be necessary as this symbol does not declare a new datatype,
  //        This symbol should only reference a Function Block declared somewhere else.
  //        If we always use the symbol->datatype annotation created in stage3 to print the datatypes
  //        this symbol will should never get called.  
  return print_token(symbol->function_block_type_name);
}



/* ref_spec:  REF_TO (non_generic_type_name | function_block_type_name) */
// SYM_REF1(ref_spec_c, type_name)
void *visit(ref_spec_c *symbol) {
  // NOTE: We must include this symbol, as REF_TO XXXX may be used anonymously (i.e. outside a datatype declaration)
  //       which means this symbol may be used as a datatype (i.e. may be pointed to by a symbol->datatype annotation)   
  
  // I don't really know the format JSON expects for pointers/references so leave it out for now
  // Nevertheless, REF_TO is only defined in version 3 of IEC 61131-3, so very little code is using it.  
  
  /*  
  s4o.print("REF_TO ");
  symbol->type_name->accept(*this);
  */
  return NULL;
}



/* ref_type_decl: identifier ':' ref_spec_init */
// SYM_REF2(ref_type_decl_c, ref_type_name, ref_spec_init)
void *visit(ref_type_decl_c *symbol) {
  return print_token(symbol->ref_type_name);
}




}; // class print_datatype_c



print_datatype_c *print_datatype_c::print_datatype = NULL; 



/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/


class print_calls_c: public iterator_visitor_c {
  private:
    stage4out_c &s4o;
    int count;  // number of POU calls already printed
    /* A symbol table with all previously printed called operations (so we don't print duplicates!)... */
    typedef symtable_c<symbol_c *> calls_symtable_t;
    calls_symtable_t calls_symtable;

  public:
    print_calls_c(stage4out_c *s4o_ptr): s4o(*s4o_ptr) {count = 0;}
    ~print_calls_c(void) {}
    
   int get_count(void) {return count;}    


  private:

    
void *print_token(symbol_c *token) {
  if (calls_symtable.count(token->token->value) != 0)
    return NULL; // already printed previously

  calls_symtable.insert(token->token->value, token);
  
  if (count != 0)
    s4o.print(",\n");
    
  s4o.print(s4o.indent_spaces + "\"");
  s4o.print(token->token->value);
  s4o.print("\"");
  count++;

  return NULL;
}


/****************************************/
/* B.2 - Language IL (Instruction List) */
/****************************************/

/***********************************/
/* B 2.1 Instructions and Operands */
/***********************************/
/* | function_name [il_operand_list] */
void *visit(il_function_call_c *symbol) {
  print_token(symbol->function_name);
  // recursively visit all ther symbols in the AST
  if (symbol->il_operand_list != NULL)
    symbol->il_operand_list->accept(*this);
  return NULL;
}


/*   il_call_operator prev_declared_fb_name
 * | il_call_operator prev_declared_fb_name '(' ')'
 * | il_call_operator prev_declared_fb_name '(' eol_list ')'
 * | il_call_operator prev_declared_fb_name '(' il_operand_list ')'
 * | il_call_operator prev_declared_fb_name '(' eol_list il_param_list ')'
 */
void *visit(il_fb_call_c *symbol) {
  print_token(symbol->fb_name);
  // recursively visit all ther symbols in the AST
  symbol->il_call_operator->accept(*this);
  if (symbol->il_operand_list != NULL)  symbol->il_operand_list->accept(*this);
  if (symbol->il_param_list   != NULL)  symbol->il_param_list  ->accept(*this);
  return NULL;
}


/* | function_name '(' eol_list [il_param_list] ')' */
void *visit(il_formal_funct_call_c *symbol) {
  print_token(symbol->function_name);
  // recursively visit all ther symbols in the AST
  if (symbol->il_param_list != NULL)   symbol->il_param_list->accept(*this);
  return NULL;
}



/***************************************/
/* B.3 - Language ST (Structured Text) */
/***************************************/
/***********************/
/* B 3.1 - Expressions */
/***********************/
void *visit(function_invocation_c *symbol) {
  print_token(symbol->function_name);
  // recursively visit all ther symbols in the AST
  /* If the syntax parser is working correctly, exactly one of the 
   * following two symbols will be NULL, while the other is != NULL.
   */
  if (symbol->   formal_param_list != NULL) symbol->   formal_param_list->accept(*this);
  if (symbol->nonformal_param_list != NULL) symbol->nonformal_param_list->accept(*this);
  return NULL;
}


/*****************************************/
/* B 3.2.2 Subprogram Control Statements */
/*****************************************/

/* fb_name '(' [param_assignment_list] ')' */
void *visit(fb_invocation_c *symbol) {
  /* NOTE: symbol->fb_name may be an array or structured variable, or a simple variable.
   *       it is NOT the FB type, but rather the FB instance.
   *       What we need to print out is the FB type!
   */
  if (NULL != symbol->called_fb_declaration)
    print_token(symbol->called_fb_declaration);
  // recursively visit all ther symbols in the AST
  /* If the syntax parser is working correctly, at most one of the 
   * following two symbols will be NULL, while the other is != NULL.
   * The two may be NULL simultaneously!
   */
  if (symbol->   formal_param_list != NULL) symbol->   formal_param_list->accept(*this);
  if (symbol->nonformal_param_list != NULL) symbol->nonformal_param_list->accept(*this);
  return NULL;
}


}; // class print_calls_c





/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/






// concatenate the name of a structured variable into a single string...
class get_multielementvar_name_c: public null_visitor_c {
  private:
    std::string name;
    bool array_found;
  
  public:
    get_multielementvar_name_c(void) {}
    ~get_multielementvar_name_c(void) {}
    
   std::string get_name(symbol_c *symbol) {
     name.clear();
     array_found = false;
     symbol->accept(*this);
     return name;
  }    

  
/*********************/
/* B 1.4 - Variables */
/*********************/
void *visit(symbolic_variable_c *symbol) {name += symbol->token->value; return NULL;}


/*************************************/
/* B.1.4.2   Multi-element Variables */
/*************************************/
/*  subscripted_variable '[' subscript_list ']' */
void *visit(array_variable_c *symbol) {
  // handle situations like 
  //  FB1.xxx_local[9].FIELDXX[2]
  //  We will be working top down when , and if we reach the outermost array 
  //     FB1.xxx_local 
  //   we have to throw away the ".FIELDXX" that has been stored in the "name" variable up to now

  // top -> down means we first recursively call the sybol, and then do the work.
  symbol->subscripted_variable->accept(*this);
  array_found = true;
  return NULL;
}

/*  record_variable '.' field_selector */
void *visit(structured_variable_c *symbol) {
  symbol->record_variable->accept(*this);
  if (!array_found) {
    name += "."; 
    name += symbol->field_selector->token->value;
  }
  return NULL;
}

}; // class get_multielementvar_name_c






class print_uses_c: public iterator_visitor_c {
  private:
    stage4out_c &s4o;
    int count;  // number of variables already printed
    /* A symbol table with all previously printed variables (so we don't print duplicates!)...
     * We start off be populating this list with all the local variables, as we simply don't
     * want any local variable to be printed out.
     */
    typedef symtable_c<symbol_c *> var_symtable_t;
    var_symtable_t var_symtable;

  public:
    print_uses_c(stage4out_c *s4o_ptr): s4o(*s4o_ptr) {count = 0;}
    ~print_uses_c(void) {}
    
   int get_count(void) {return count;}    
   
   void handle_pou(symbol_c *pou_decl, symbol_c *pou_body) {
     /* We only want to print the non local variables,
      * so we start off by adding all the local variables to 
      * the list of variables we will NOT be printing out
      * (or that have alreay been printed out once, so should
      *  not be printed out again).
      */
     localvar_list_builder_c localvar_list_builder;
     localvar_list_builder.populate_list(&var_symtable, pou_decl);
     pou_body->accept(*this);
     return;
   }


  private:

    
void *print_token(const char *varname, symbol_c *symbol) {
  if (var_symtable.count(varname) != 0)
    return NULL; // already printed previously, or is a local variable

  char *varx = strdup(varname);
  if (NULL == varx) ERROR;
  
  char *tmp = strchr(varx, '.')  ;
  if (tmp != NULL) {
    /* varname is a structured variable, e.g. 'varx.field1.field2'
     * If it is a local variable it would have been declared as only
     * 'varx', so we must search for 'varx' by itself in the var_symtable
     */
    // the following code is ugly, but let's get it working quickly...
    *tmp = '\0';
    if (var_symtable.count(varx) != 0) {
      free(varx);
      return NULL; // already printed previously, or is a local variable
    }
  }
  free(varx);
  
  var_symtable.insert(varname, symbol);
  
  if (count != 0)
    s4o.print(",\n");
    
  s4o.print(s4o.indent_spaces + "\"");
  s4o.print(varname);
  s4o.print("\"");
  count++;

  return NULL;
}


/*********************/
/* B 1.4 - Variables */
/*********************/
void *visit(symbolic_variable_c *symbol) {return print_token(symbol->var_name->token->value, symbol);}
void *visit(symbolic_constant_c *symbol) {return print_token(symbol->var_name->token->value, symbol);}

/********************************************/
/* B.1.4.1   Directly Represented Variables */
/********************************************/
void *visit(direct_variable_c *symbol) {return print_token(symbol->token->value, symbol);}


/*************************************/
/* B.1.4.2   Multi-element Variables */
/*************************************/
/*  subscripted_variable '[' subscript_list ']' */
void *visit(array_variable_c *symbol) {
  // TODO: How to handle array variables??
  /*
  symbol->subscripted_variable->accept(*this);
  s4o.print("[");
  symbol->subscript_list->accept(*this);
  s4o.print("]");
  */
  // TODO: a subscripted_variable may be a structured_variable_c, 
  //       in which case symbol->subscripted_variable->token will NULL,
  //       We must therefore call get_multielementvar_name_c
  get_multielementvar_name_c get_multielementvar_name;
  print_token(get_multielementvar_name.get_name(symbol->subscripted_variable).c_str(), symbol);
  // recursively visit the subscript_list as it may contain more variable accesses
  // (for example, inside expressions used as an index to an array: 
  //     v1.v2.v3[ 55 + v4].v5
  //     by visiting subscript_list we will catch v4
  //  )
  
  
  //symbol->subscript_list->accept(*this);
  return NULL;
}


/*  record_variable '.' field_selector */
void *visit(structured_variable_c *symbol) {
  // DO NOT descend down the rabbit hole!
  // the first time this visitor gets called (i.e. the top most variable)
  // we do NOT recursvly call the record variable.
  get_multielementvar_name_c get_multielementvar_name;
  print_token(get_multielementvar_name.get_name(symbol).c_str(), symbol);
  return NULL;
}





}; // class print_uses_c






/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/






class generate_operation_c: public iterator_visitor_c {
//class generate_operation_c: public visitor_c {
  private:
    stage4out_c       &s4o;

    
    
    
void *print_token(symbol_c *token) {
  return s4o.print(token->token->value);
}


void *print_datatype(symbol_c *datatype) {
  return datatype->accept(*(print_datatype_c::get_singleton()));
}



void *print_args(symbol_c *pou_decl) {
  function_param_iterator_c param_iter(pou_decl);
  int count = 0;
  
  while (param_iter.next() != NULL) {
    if (param_iter.is_extensible_param     ()) break;
    if (param_iter.is_en_eno_param_implicit()) continue;
    
    function_param_iterator_c::param_direction_t parmdir;
    parmdir = param_iter.param_direction();
    if   ((parmdir != function_param_iterator_c::direction_out) 
       && (parmdir != function_param_iterator_c::direction_inout)
       && (parmdir != function_param_iterator_c::direction_in))
      continue;
    
    if (count != 0)
      s4o.print(",\n");
      
    s4o.print(s4o.indent_spaces + "\"");
    print_datatype(param_iter.param_type());
    s4o.print("\"");
    count++;
  }
  if (count != 0)
    s4o.print("\n");
  return NULL;
}



void *print_calls(symbol_c *pou_decl) {
  print_calls_c print_calls(&s4o);
  
  pou_decl->accept(print_calls);
  if (print_calls.get_count() > 0)
    s4o.print("\n");
  
  return NULL;
}



void *print_uses(symbol_c *pou_decl, symbol_c *pou_body) {
  print_uses_c print_uses(&s4o);
  print_uses.handle_pou(pou_decl, pou_body);
  s4o.print("\n");
  return NULL;
}



void *print_operation(symbol_c *pou_decl,
                      symbol_c *name,
                      symbol_c *returns,
                      symbol_c *pou_body
                     ) {
  if (!enable_print) 
    return NULL;
  
  if (entry_count++ != 0) 
    s4o.print(",");
  s4o.print("\n");
  s4o.print(s4o.indent_spaces + "{\n");
  s4o.indent_right();

    s4o.print(s4o.indent_spaces + "\"operation\": {\n");
    s4o.indent_right();

      // "name": "<string>",
      s4o.print(s4o.indent_spaces + "\"name\": \"");
      print_token(name);
      s4o.print("\",\n");
    
      // "returns": "<datatype>",
      if (NULL != returns) {
        s4o.print(s4o.indent_spaces + "\"returns\": \"");
        print_datatype(returns);
        s4o.print("\",\n");
      }
  
      // "args": [
      //    "<datatype>"
      //  ],
      s4o.print(s4o.indent_spaces + "\"args\": [\n");
      s4o.indent_right();
      print_args(pou_decl);
      s4o.indent_left();    
      s4o.print(s4o.indent_spaces + "],\n");

      // "creates": [
      //    "<datatype>"
      //  ],
      s4o.print(s4o.indent_spaces + "\"creates\": [\n");
      s4o.indent_right();
      // visit...
      s4o.indent_left();    
      s4o.print(s4o.indent_spaces + "],\n");

      // "destroys": [
      //    "<datatype>"
      //  ],
      s4o.print(s4o.indent_spaces + "\"destroys\": [\n");
      s4o.indent_right();
      // visit...
      s4o.indent_left();    
      s4o.print(s4o.indent_spaces + "],\n");
      
      // "calls": [
      //    "<operation>"
      //  ],
      s4o.print(s4o.indent_spaces + "\"calls\": [\n");
      s4o.indent_right();
      print_calls(pou_decl);
      s4o.indent_left();    
      s4o.print(s4o.indent_spaces + "],\n");
      
      // "uses": [
      //    "<data>"
      //  ],
      s4o.print(s4o.indent_spaces + "\"uses\": [\n");
      s4o.indent_right();
      print_uses(pou_decl, pou_body);  
      s4o.indent_left();    
      s4o.print(s4o.indent_spaces + "],\n");
      
      // "hints": {
      //    XXX
      //  },
      s4o.print(s4o.indent_spaces + "\"hints\": {\n");
      s4o.indent_right();
      // visit...
      s4o.indent_left();    
      s4o.print(s4o.indent_spaces + "}\n");
      
    s4o.indent_left();    
    s4o.print(s4o.indent_spaces + "}\n");  // "operation":

  s4o.indent_left();    
  s4o.print(s4o.indent_spaces + "}");
  
  return NULL;
}
    
    
  public:
    generate_operation_c(stage4out_c *s4o_ptr): s4o(*s4o_ptr) {}
    ~generate_operation_c(void) {}

    
/********************/
/* 2.1.6 - Pragmas  */
/********************/
void *visit( enable_code_generation_pragma_c * symbol)  {s4o. enable_output(); enable_print = true;  return NULL;}
void *visit(disable_code_generation_pragma_c * symbol)  {s4o.disable_output(); enable_print = false; return NULL;} 
    
    

/***********************/
/* B 1.5.1 - Functions */
/***********************/
/*  FUNCTION derived_function_name ':' elementary_type_name io_OR_function_var_declarations_list function_body END_FUNCTION */
void *visit(function_declaration_c *symbol) {
  return print_operation(symbol, symbol->derived_function_name, symbol->type_name, symbol->function_body);
}


/*****************************/
/* B 1.5.2 - Function Blocks */
/*****************************/
/*  FUNCTION_BLOCK derived_function_block_name io_OR_other_var_declarations fblock_body END_FUNCTION_BLOCK */
void *visit(function_block_declaration_c *symbol) {
  return print_operation(symbol, symbol->fblock_name, NULL, symbol->fblock_body);
}


/**********************/
/* B 1.5.3 - Programs */
/**********************/
/*  PROGRAM program_type_name program_var_declarations_list function_block_body END_PROGRAM */
void *visit(program_declaration_c *symbol) {
  return print_operation(symbol, symbol->program_type_name, NULL, symbol->function_block_body);
}




}; /* class generate_operation_c */

/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/






class generate_data_c: public iterator_visitor_c {
  private:
    stage4out_c       &s4o;
    symbol_c *current_param_type;

    
void *print_token(symbol_c *token) {
  return s4o.print(token->token->value);
}


void *print_datatype(symbol_c *datatype) {
  return datatype->accept(*(print_datatype_c::get_singleton()));
}



void *print_data(symbol_c *name,
                 symbol_c *datatype
                 ) {
  if (!enable_print) 
    return NULL;
  
  if (entry_count++ != 0) 
    s4o.print(",");
  s4o.print("\n");
  s4o.print(s4o.indent_spaces + "{\n");
  s4o.indent_right();

    s4o.print(s4o.indent_spaces + "\"data\": {\n");
    s4o.indent_right();

      // "name": "<string>",
      s4o.print(s4o.indent_spaces + "\"name\": \"");
      print_token(name);
      s4o.print("\",\n");
    
      // "datatype": "<datatype>",
      if (NULL != datatype) {
        s4o.print(s4o.indent_spaces + "\"datatype\": \"");
        print_datatype(datatype);
        s4o.print("\",\n");
      }
  
      // "records": [
      //    {
      //      "name": "<name>"
      //      "datatype": "<datatype>",
      //    }
      //  ],
      s4o.print(s4o.indent_spaces + "\"records\": [\n");
      s4o.print(s4o.indent_spaces + "],\n");

      // "hints": {
      //    "module": "<module_name>"
      //    "imports": [
      //                "<string>"
      //               ],
      //  }
      s4o.print(s4o.indent_spaces + "\"hints\": {\n");
      s4o.print(s4o.indent_spaces + "}\n");
      
    s4o.indent_left();    
    s4o.print(s4o.indent_spaces + "}\n");  // "data":

  s4o.indent_left();    
  s4o.print(s4o.indent_spaces + "}");
  
  return NULL;
}
    
    
  public:
    generate_data_c(stage4out_c *s4o_ptr): s4o(*s4o_ptr) {current_param_type = NULL;}
    ~generate_data_c(void) {}

    
    
    
    
    
    
/********************/
/* 2.1.6 - Pragmas  */
/********************/
void *visit( enable_code_generation_pragma_c * symbol)  {s4o. enable_output(); enable_print = true;  return NULL;}
void *visit(disable_code_generation_pragma_c * symbol)  {s4o.disable_output(); enable_print = false; return NULL;} 
    
    

    
/******************************************/
/* B 1.4.3 - Declaration & Initialisation */
/******************************************/
/*| VAR_GLOBAL [CONSTANT|RETAIN] global_var_decl_list END_VAR */
/* option -> may be NULL ! */
void *visit(global_var_declarations_c *symbol) {
  return symbol->global_var_decl_list->accept(*this);
}

/* helper symbol for global_var_declarations */
/*| global_var_decl_list global_var_decl ';' */
// void *visit(global_var_decl_list_c *symbol) {}
//  use inherited iterator implementation

    
/*| global_var_spec ':' [located_var_spec_init|function_block_type_name] */
/* type_specification ->may be NULL ! */
void *visit(global_var_decl_c *symbol) {
  current_param_type = spec_init_sperator_c::get_spec(symbol->type_specification);  
  symbol->global_var_spec->accept(*this);
  current_param_type = NULL;
  return NULL;
}
    
    
/*| global_var_name location */
void *visit(global_var_spec_c *symbol) {
  print_data(symbol->global_var_name, current_param_type);
  //symbol->location->accept(*this);
  return NULL;
}
    

/*| global_var_list ',' global_var_name */
void *visit(global_var_list_c *symbol) {
  for(int i = 0; i < symbol->n; i++) {
    print_data(symbol->get_element(i), current_param_type);
  }
  return NULL;
}
    
    
    
/***********************/
/* B 1.5.1 - Functions */
/***********************/
/*  FUNCTION derived_function_name ':' elementary_type_name io_OR_function_var_declarations_list function_body END_FUNCTION */
void *visit(function_declaration_c *symbol) {return NULL;}


/*****************************/
/* B 1.5.2 - Function Blocks */
/*****************************/
/*  FUNCTION_BLOCK derived_function_block_name io_OR_other_var_declarations fblock_body END_FUNCTION_BLOCK */
void *visit(function_block_declaration_c *symbol) {return NULL;}


/**********************/
/* B 1.5.3 - Programs */
/**********************/
/*  PROGRAM program_type_name program_var_declarations_list function_block_body END_PROGRAM */
void *visit(program_declaration_c *symbol) {return NULL;}




/********************************/
/* B 1.7 Configuration elements */
/********************************/
/*
CONFIGURATION configuration_name
   optional_global_var_declarations
   (resource_declaration_list | single_resource_declaration)
   optional_access_declarations
   optional_instance_specific_initializations
END_CONFIGURATION
*/
void *visit(configuration_declaration_c *symbol) {
  if (symbol->global_var_declarations != NULL)
    symbol->global_var_declarations->accept(*this);
  symbol->resource_declarations->accept(*this);
  return NULL;
}


/* intermediate helper symbol for configuration_declaration  */
/*  { global_var_declarations_list }   */
//void *visit(global_var_declarations_list_c *symbol) {return print_list(symbol);}
//  use inherited iterator implementation

/* helper symbol for configuration_declaration */
/*| resource_declaration_list resource_declaration */
//void *visit(resource_declaration_list_c *symbol) {return print_list(symbol);}
//  use inherited iterator implementation


/*
RESOURCE resource_name ON resource_type_name
   optional_global_var_declarations
   single_resource_declaration
END_RESOURCE
*/
void *visit(resource_declaration_c *symbol) {
  STAGE4_WARNING(symbol, symbol, "resource declaratons are not yet supported.\n");
  return NULL;
}



}; /* class generate_data_c */



/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/


class generate_json_c: public null_visitor_c {
  private:
    stage4out_c &s4o;


  public:
    generate_json_c(stage4out_c *s4o_ptr): s4o(*s4o_ptr) {}
    ~generate_json_c(void) {}



/***************************/
/* B 0 - Programming Model */
/***************************/
void *visit(library_c *symbol) {
  generate_data_c      generate_data     (&s4o);
  generate_operation_c generate_operation(&s4o);
  
  s4o.print(s4o.indent_spaces + "[");
  s4o.indent_right();
  symbol->accept(generate_data); 
  symbol->accept(generate_operation);
  s4o.print("\n");
  s4o.indent_left();    
  s4o.print(s4o.indent_spaces + "]\n");
  
  return NULL;
}


}; /* class generate_json_c */



/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/
/***********************************************************************/




visitor_c *new_code_generator(stage4out_c *s4o, const char *builddir)  {
    _s4o = s4o;
    return new generate_json_c(s4o);
}
void delete_code_generator(visitor_c *code_generator) {delete code_generator;}















